package cn.hyatt.auth.service.impl;

import cn.hyatt.auth.config.JwtProperties;
import cn.hyatt.auth.dto.auth.AuthInfo;
import cn.hyatt.auth.dto.auth.UserInfo;
import cn.hyatt.auth.dto.auth.UsernameLoginRequest;
import cn.hyatt.auth.dto.auth.VueRouter;
import cn.hyatt.auth.dto.systemDept.SystemDeptVo;
import cn.hyatt.auth.dto.systemRole.SystemRoleVo;
import cn.hyatt.auth.dto.systemUser.SystemUserVo;
import cn.hyatt.auth.entity.SystemDept;
import cn.hyatt.auth.entity.SystemMenu;
import cn.hyatt.auth.entity.SystemRole;
import cn.hyatt.auth.enums.MenuType;
import cn.hyatt.auth.event.AuthEvent;
import cn.hyatt.auth.security.entity.LoginUserDetails;
import cn.hyatt.auth.service.*;
import cn.hyatt.auth.utils.SecurityUtil;
import cn.hyatt.common.exception.BusinessException;
import cn.hyatt.common.utils.JwtUtil;
import cn.hyatt.common.utils.KaptchaUtil;
import cn.hyatt.common.utils.StringTool;
import cn.hyatt.core.entity.BaseEntity;
import com.alibaba.fastjson.JSONObject;
import com.google.code.kaptcha.Producer;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 认证-服务实现
 *
 * @author hyatt
 */
@Slf4j
@Service
@Primary
@AllArgsConstructor
public class AuthServiceImpl implements AuthService {

    private ApplicationEventPublisher publisher;
    private AuthenticationManager authenticationManager;
    private JwtProperties jwtProperties;
    private SystemUserService systemUserService;
    private SystemRoleService systemRoleService;
    private SystemMenuService systemMenuService;
    private SystemDeptService systemDeptService;
    private AuthCacheService authCacheService;

    @Override
    public AuthInfo usernameLogin(HttpServletRequest request, UsernameLoginRequest loginRequest) {
        // 验证码校验
        Optional<String> optional = authCacheService.getCaptcha(request);
        String captcha = optional.orElseThrow(() -> new BusinessException("验证码错误，请刷新验证码重试!"));
        if (!captcha.equals(loginRequest.getCaptcha())) {
            throw new BusinessException("验证码错误，请刷新验证码重试!");
        }

        // 封装 SpringSecurity 验证响应请求
        Authentication authenticationRequest = UsernamePasswordAuthenticationToken
                .unauthenticated(loginRequest.getUsername(), loginRequest.getPassword());
        // 身份验证响应
        Authentication authenticationResponse = authenticationManager
                .authenticate(authenticationRequest);
        // 登录用户详细信息
        LoginUserDetails userDetails = (LoginUserDetails) authenticationResponse.getPrincipal();
        // 返回对象
        AuthInfo authInfo = getCacheToken(userDetails);
        // 发布用户登录成功事件
        publisher.publishEvent(new AuthEvent.LoginSuccess(userDetails.getId(),
                userDetails.getUsername(), authInfo));
        return authInfo;
    }

    @Override
    public void logout() {
        Long userId = SecurityUtil.getUserId();
        systemUserService.deleteCacheById(userId);
        // 发布足校登录成功事件
        publisher.publishEvent(new AuthEvent.Logout(userId));
    }

    @Override
    public AuthInfo refreshAuth(HttpServletRequest request) {
        String tokenHeader = request.getHeader(jwtProperties.getTokenHeader());
        // 获取token内容
        String token = StringTool.removePrefix(tokenHeader, jwtProperties.getTokenPrefix());
        // 获取请求头参数
        Claims claims;
        try {
            claims = JwtUtil.parseJWT(jwtProperties.getSecretKey(), token);
        } catch (ExpiredJwtException e) {
            // 认证超时
            log.info("认证超时:{}", e.getMessage());
            throw new BusinessException("认证超时:" + e.getMessage());
        } catch (IllegalArgumentException e) {
            // 非法参数异常
            log.info("非法参数异常:{}", e.getMessage());
            throw new BusinessException("非法参数异常:" + e.getMessage());
        }
        // 获取登录用户认证
        Long userId = ((Double) claims.get("id")).longValue();
        Optional<AuthInfo> authInfoOptional = authCacheService.getAuthInfo(userId);
        AuthInfo cacheAuthInfo = authInfoOptional
                .orElseThrow(() -> new BusinessException("认证信息不存在，请重新登录"));
        if (!token.equals(cacheAuthInfo.getRefreshToken())) {
            throw new BusinessException("认证信息异常，请重新登录");
        }
        // 获取用户信息
        Optional<SystemUserVo> optional = systemUserService.getCacheById(userId);
        if (optional.isEmpty()) {
            throw new BusinessException("登录用户信息异常-请重新登录");
        }
        LoginUserDetails userDetails = new LoginUserDetails();
        BeanUtils.copyProperties(optional.get(), userDetails);
        // 返回对象
        AuthInfo authInfo = getCacheToken(userDetails);
        // 发布刷新Token事件
        publisher.publishEvent(new AuthEvent.FlushedToken(userId, authInfo));
        return authInfo;
    }

    @Override
    public String getBase64Captcha(HttpServletRequest request) throws IOException {
        return Base64.encodeBase64String(createCaptchaImageOutputStream(request).toByteArray());
    }

    /**
     * 创建验证码图片
     *
     * @param request HTTP 请求
     * @return 图片
     */
    public ByteArrayOutputStream createCaptchaImageOutputStream(HttpServletRequest request) throws IOException {
        // 获取请求用户Session信息
        HttpSession session = request.getSession();
        String sessionId = session.getId();
        // 生成验证码内容
        Producer producer = KaptchaUtil.buildProducer(5);
        String captcha = producer.createText();
        System.out.println("-----------验证码---------");
        System.out.println(captcha);
        System.out.println("-------------------------");
        // 保存验证码内容
        authCacheService.saveCaptcha(sessionId, captcha);
        // 生成验证码图片
        BufferedImage captchaImage = producer.createImage(captcha);
        // 转换为 Output
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(captchaImage, "png", outputStream);
        return outputStream;
    }

    @Override
    public UserInfo getUserInfo(Long userId) {
        // 获取当前用户
        Optional<SystemUserVo> optional = systemUserService.getCacheById(userId);
        SystemUserVo vo = optional.orElseThrow(() -> new BusinessException("数据异常，请联系管理员"));

        // 创建对象
        UserInfo userInfo = new UserInfo();
        BeanUtils.copyProperties(vo, userInfo);
        // 获取用户Dept
        SystemDept deptEntity = systemDeptService.getById(vo.getDeptId());
        SystemDeptVo deptVo = systemDeptService.toVo(deptEntity);
        userInfo.setDept(deptVo);
        // 获取用户角色
        List<SystemRole> roleEntityList = systemRoleService.findByUserId(userId);
        List<SystemRoleVo> roleList = roleEntityList.stream().map(systemRoleService::toVo).toList();
        userInfo.setRoles(roleList);

        return userInfo;
    }

    @Override
    public List<VueRouter> getRouters(Long userId) {
        // 获取用户角色
        List<SystemRole> roleEntityList = systemRoleService.findByUserId(userId);
        List<Long> roleIdList = roleEntityList.stream().map(BaseEntity::getId).toList();
        // 获取菜单
        List<MenuType> menuTypeList = List.of(MenuType.DIRECTORY, MenuType.MENU);
        List<SystemMenu> menuList = systemMenuService.findByTypeAndRole(menuTypeList, roleIdList);
        return null;
    }


    /**
     * 获取Token
     *
     * @param userDetails 用户信息
     * @return 结果
     */
    private AuthInfo getCacheToken(LoginUserDetails userDetails) {
        // 创建 Token
        String json = JSONObject.toJSONString(userDetails);
        Map<String, Object> map = JSONObject
                .parseObject(json)
                .getInnerMap();
        map.put("issuer", jwtProperties.getIssuer());
        map.remove("password");

        // 创建Token 与 刷新Token
        String accessToken = JwtUtil.createJWT(jwtProperties.getSecretKey(), jwtProperties.getValidDuration(), map);
        LocalDateTime expireTime = LocalDateTime.now().plusSeconds(jwtProperties.getValidDuration() / 1000);
        String refreshToken = JwtUtil.createJWT(jwtProperties.getSecretKey(), jwtProperties.getRefreshDuration(), map);
        AuthInfo authInfo = new AuthInfo();
        authInfo.setAccessToken(accessToken);
        authInfo.setRefreshToken(refreshToken);
        authInfo.setExpireTime(expireTime.toInstant(ZoneOffset.of("+8")).toEpochMilli());
        return authInfo;
    }
}
